part of hidden;

class Rendered {
   PreElement vis;
   DivElement cur;
   Internal intr;

   Rendered (DivElement cont) {
      // instantiate
      vis = new PreElement();
      cur = new DivElement();
      intr = new Internal();

      // styling
      cont.style
         ..boxSizing = 'border-box'
         ..display = 'flex'
         ..overflow = 'auto'
         ..position = 'relative';
      vis.style
         ..boxSizing = 'border-box'
         ..margin = '0'
         ..display = 'flex'
         ..flexGrow = '1'
         ..flexShrink = '0'
         ..flexDirection = 'column'
         ..alignItems = 'flex-start'
         ..fontFamily = 'inherit'
         ..fontSize = 'inherit';
      cur.style
         ..width = '1px'
         ..height = '1em'
         ..backgroundColor = 'black'
         ..position = 'absolute'
         ..top = '0em'
         ..left = '0em';

      // handlers
      cont.attributes['tabIndex'] = '0';
      cont.onKeyDown.listen(handleKeyDown);

      // insert into DOM
      List<Element> childs = cont.children;
      if (childs.length != 0) throw new ArgumentError('container must be an empty div element');
      vis.children.add(Rendered.makeSpan(''));
      childs.add(vis);
      childs.add(cur);
   }

   // handlers, the keyDown handler is HUGE
   void handleKeyDown (KeyboardEvent ke) {
      ke.preventDefault();
      if (ke.altKey || ke.ctrlKey || ke.metaKey) { return; }
      int kc = ke.keyCode;

      // single character
      if (Keyboard.lower.containsKey(kc)) {
         String c;
         if (ke.shiftKey) c = Keyboard.upper[kc];
         else c = Keyboard.lower[kc];
         intr.insertChar(c);
         updateSpan(intr.anch.line);
         updateCursor();
         return;
      }

      // arrow keys
      if (kc == 37) { intr.left(); updateCursor(); return; }
      if (kc == 38) { intr.up(); updateCursor(); return; }
      if (kc == 39) { intr.right(); updateCursor(); return; }
      if (kc == 40) { intr.down(); updateCursor(); return; }

      // newline
      if (kc == 13) {
         intr.insertNewline();
         updateSpan(intr.anch.line - 1);
         insertSpan(intr.anch.line);
         updateCursor();
         return;
      }

      // backspace
      if (kc == 8) {
         if (intr.anch.line == 0 && intr.anch.char == 0) return;
         else if (intr.anch.char == 0) { 
            intr.removeNewline();
            updateSpan(intr.anch.line);
            removeSpan(intr.anch.line + 1);
         }
         else {
            intr.removeChar();
            updateSpan(intr.anch.line);
         }
         updateCursor();
         return;
      }
   }

   // update/insert/remove spans
   void updateSpan (int line) {
      vis.children[line].text = intr.lines[line];
   }
   
   void insertSpan (int line) {
      SpanElement se = Rendered.makeSpan(intr.lines[line]);
      List<SpanElement> ses = vis.children;
      if (ses.length == line) ses.add(se);
      else ses.insert(line, se);
   }

   void removeSpan (int line) {
      vis.children.removeAt(line);
   }

   // cursors, anch and head
   void updateCursor () {
      cur.style.top = (intr.anch.line).toString() + 'em';
      cur.style.left = (intr.anch.char/2.0).toString() + 'em';
   }

   // static methods, didn't wanna put them in a 'helper' class
   static SpanElement makeSpan (String text) {
      SpanElement se = new SpanElement();
      se.style.flexShrink = '0';
      se.style.flexBasis = '1em';
      se.text = text;
      return se;
   }
}
